package com.intermancer.predictor.experiment.prime;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import org.apache.commons.lang.StringUtils;

import com.intermancer.predictor.evaluator.AbsoluteDifferenceCE;
import com.intermancer.predictor.feeder.BufferedFeeder;
import com.intermancer.predictor.feeder.Feeder;
import com.intermancer.predictor.feeder.SimpleRF;
import com.intermancer.predictor.inject.DefaultInjectStrategy;
import com.intermancer.predictor.inject.InjectStrategy;
import com.intermancer.predictor.lifecycle.DefaultLifecycleDirectorFactory;
import com.intermancer.predictor.lifecycle.InjectCommand;
import com.intermancer.predictor.lifecycle.LifecycleContext;
import com.intermancer.predictor.lifecycle.LifecycleDirector;
import com.intermancer.predictor.lifecycle.LifecycleListener;
import com.intermancer.predictor.organism.Organism;
import com.intermancer.predictor.organism.OrganismRegistry;
import com.intermancer.predictor.store.CommentedStoreEntry;
import com.intermancer.predictor.store.StoreEntry;
import com.intermancer.predictor.store.StoreEntryDAO;
import com.intermancer.predictor.store.simple.StoreEntryFileDAO;
import com.intermancer.predictor.store.simple.StoreEntryInMemoryDAO;
import com.intermancer.predictor.system.InitializationException;
import com.intermancer.predictor.system.adamic.AdamicOR;

/**
 * This is the first Experiment. It evolves organisms for predicting the value
 * of a given channel in a data file.
 * 
 * @author johnfryar
 * 
 */
public class ExperimentPrime {

	private class NewAlphaLifecycleListener implements LifecycleListener {

		public boolean isActivated = false;
		public double lowestScore = 0.0;

		@Override
		public void handleEvent(LifecycleContext context) {
			if (isActivated) {
				for (StoreEntry storeEntry : context.getStoredEntries()) {
					if (storeEntry.getScore() <= lowestScore) {
						CommentedStoreEntry commentedStoreEntry = new CommentedStoreEntry(
								storeEntry);
						commentedStoreEntry.setComment("Lifecycle count: "
								+ lifecycleCount);
						repositoryStore.put(commentedStoreEntry);
						lowestScore = storeEntry.getScore();
					}
				}
			}
		}

	}

	private static final int DEFAULT_WORKING_STORE_SIZE = 500;
	private static final int DEFAULT_REPOSITORY_STORE_SIZE = 20;
	private static final String REPO_PREFIX = "Repo-";
	private static final String WORK_PREFIX = "Work-";

	private StoreEntryDAO workingStore;
	private StoreEntryDAO repositoryStore;

	private Feeder feeder;

	private LifecycleDirector lifecycleDirector;
	private NewAlphaLifecycleListener newAlphaLifecycleListener;
	private int lifecycleCount;

	public static void main(String[] args) {
		ExperimentPrime experiment = new ExperimentPrime();
		Context context = new Context();
		if (OptionHandler.handleArguments(args, context)) {
			experiment.execute(context);
		}
	}

	public void execute(Context context) {
		initializeStores(context);
		initializeFeeder(context);
		initializeLifecycleDirector(context);
		executeExperiment(context);
	}

	private void initializeStores(Context context) {
		if (workingStore == null) {
			if (context.getWorkingStoreSize().equals(new Integer(0))) {
				context.setWorkingStoreSize(DEFAULT_WORKING_STORE_SIZE);
			}
			workingStore = new StoreEntryInMemoryDAO(context.getWorkingStoreSize());
		}
		if (repositoryStore == null) {
			if (StringUtils.isBlank(context.getRepositoryPath())) {
				throw new InitializationException(
						"Either repositoryStore or repositoryPath must be initialized.");
			}
			repositoryStore = new StoreEntryFileDAO(context.getRepositoryPath());
		}
	}

	private void initializeFeeder(Context context) {
		if (feeder == null) {
			if (context.getDataSourcePath() == null || context.getChannelOffset() < 0
					|| context.getLookahead() == 0) {
				throw new InitializationException(
						"Either feeder or dataSourcePath, channel, and lookahead must be initialized.");
			}
			Reader fileReader = getFileReader(context.getDataSourcePath());
			feeder = new SimpleRF(fileReader);
			feeder = new BufferedFeeder(feeder);

			AbsoluteDifferenceCE evaluator = new AbsoluteDifferenceCE(context.getChannelOffset(),
					context.getLookahead());
			feeder.addFeedCycleListener(evaluator);
		}
	}

	private static Reader getFileReader(String filePath) {
		Reader fileReader = null;
		try {
			fileReader = new InputStreamReader(new FileInputStream(filePath));
		} catch (IOException e) {
			e.printStackTrace();
		}
		return fileReader;
	}

	private void initializeLifecycleDirector(Context context) {
		DefaultLifecycleDirectorFactory factory = new DefaultLifecycleDirectorFactory(
				workingStore, feeder);
		newAlphaLifecycleListener = new NewAlphaLifecycleListener();
		factory.addLifecycleListener(InjectCommand.class,
				newAlphaLifecycleListener);
		lifecycleDirector = factory.getLifecycleDirector();
	}

	private void executeExperiment(Context context) {
		try {
			if (context.getLifecycles().equals(new Integer(0))) {
				throw new InitializationException(
						"lifecycles must be initialized.");
			}
			fillWorkingStore(context);
			for (lifecycleCount = 0; lifecycleCount < context.getLifecycles(); lifecycleCount++) {
				lifecycleDirector.executeLifecycle();
				if((lifecycleCount % 100) == 0) {
					System.out.println("lifecycleCount: " + lifecycleCount);
				}
			}
		} catch (Exception exception) {
			exception.printStackTrace(System.out);
		}
	}

	private void fillWorkingStore(Context context) throws Exception {
		initializeWithInitialOrganisms();
		fillWithChildren(context);
		StoreEntry bestStoreEntry = workingStore.getByScoreIndex(0);
		newAlphaLifecycleListener.lowestScore = bestStoreEntry.getScore();
		CommentedStoreEntry commentedStoreEntry = new CommentedStoreEntry(
				bestStoreEntry);
		commentedStoreEntry.setComment("Initial storeEntry");
		repositoryStore.put(commentedStoreEntry);
		newAlphaLifecycleListener.isActivated = true;
	}

	private void initializeWithInitialOrganisms() {
		InjectStrategy injectStrategy = new DefaultInjectStrategy();
		OrganismRegistry registry = new AdamicOR();
		for (Organism organism : registry.getOrganisms()) {
			feedEvaluateAndStore(injectStrategy, organism);
		}
	}

	private void fillWithChildren(Context context) throws Exception {
		while (workingStore.getSize() < workingStore.getMaxSize()) {
			lifecycleDirector.executeLifecycle();
		}
	}

	private void feedEvaluateAndStore(InjectStrategy injectStrategy,
			Organism organism) {
		StoreEntry storeEntry = new StoreEntry();
		storeEntry.setOrganism(organism);
		feeder.setOrganism(organism);
		feeder.init();
		feeder.feedOrganism();
		storeEntry.setScore(feeder.getEvaluator().getScore());
		injectStrategy.inject(storeEntry, workingStore);
	}

	public void setWorkingStore(StoreEntryDAO workingStore) {
		this.workingStore = workingStore;
	}

	public void setRepositoryStore(StoreEntryDAO repositoryStore) {
		this.repositoryStore = repositoryStore;
	}

	public void setFeeder(Feeder feeder) {
		this.feeder = feeder;
	}

}
